package ink.xiaobaibai.autoConfig;

import ink.xiaobaibai.autoConfig.exception.AutoConfigException;
import ink.xiaobaibai.autoConfig.role.RoleItem;
import ink.xiaobaibai.autoConfig.roleResources.ResourcesRoleMapping;
import ink.xiaobaibai.entity.AdminResources;
import ink.xiaobaibai.entity.AdminRole;
import ink.xiaobaibai.entity.AdminRoleResources;
import ink.xiaobaibai.service.IAdminResourcesService;
import ink.xiaobaibai.service.IAdminRoleResourcesService;
import ink.xiaobaibai.service.IAdminRoleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.SpringProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description: 自动配置中心
 * @author: 小白白
 * @create: 2021-05-18
 **/

@Configuration
@Slf4j
public class AutoConfig implements ApplicationRunner {

    @Value("${server.servlet.context-path}")
    private String baseUri;

    @Autowired
    private IAdminRoleService adminRoleService;

    @Autowired
    private IAdminResourcesService adminResourcesService;

    @Autowired
    private IAdminRoleResourcesService adminRoleResourcesService;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private AutoConfigProperties autoConfigProperties;

    @Autowired
    private AutoConfig autoConfig;

    private List<RoleItem> adminRoleList;

    @Override
    public void run(ApplicationArguments args) {
        boolean isInit = this.autoConfigProperties.isInit();
        if (isInit) {
            this.adminRoleList = this.autoConfigProperties.getRoleItemList();
            //防止回滚失效
            this.autoConfig.init();
        }
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public void init() {
        //防止回滚失效
        this.autoConfig.initRole();
        this.autoConfig.initRoleMapResources();
    }

    @Transactional(rollbackFor = RuntimeException.class)
    void initRole() {
        if (this.adminRoleList == null) {
            this.adminRoleList = new ArrayList<>();
        }
        List<AdminRole> list = this.adminRoleList.stream().map(f -> {
            if (f.roleLevel <= 1) {
                throw new AutoConfigException("角色等级不可小于等于1");
            }
            return new AdminRole(f.roleName, f.roleLevel);
        }).collect(Collectors.toList());
        //role_name唯一
        //添加管理员
        list.add(0, new AdminRole("admin", 1));
        //配置角色
        this.adminRoleService.saveBatch(list);
    }

    /**
     * 开启自动配置角色-资源
     */
    @Transactional(rollbackFor = RuntimeException.class)
    AutoConfig initRoleMapResources() {

        //待存入资源
        List<AdminResources> adminResourcesList = new ArrayList<>();

        //roleName-id
        List<AdminRole> list = this.adminRoleService.list();
        Map<String, Integer> roleIdMap = new HashMap<>(list.size());
        for (AdminRole adminRole : list) {
            roleIdMap.put(adminRole.getRoleName(), adminRole.getId());
        }

        //resources-roleId
        Map<AdminResources, Set<Integer>> adminResourcesSetMap = new HashMap<>();

        String baseUri = this.baseUri;

        //被RestController修饰的bean
        Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(RestController.class);

        for (Map.Entry<String, Object> entry : beans.entrySet()) {

            Class<?> beanClass = entry.getValue().getClass();

            for (Class<?> anInterface : beanClass.getInterfaces()) {
                if (anInterface == SpringProxy.class) {
                    //当前类是代理对象,将其转为原对象
                    beanClass = beanClass.getSuperclass();
                }
            }

            //次级uri
            String[] secondaryUris = getRequestMappingValuesWithClass(beanClass);

            Method[] methods = beanClass.getDeclaredMethods();

            for (Method method : methods) {

                String[] uris = getRequestMappingValuesWithMethod(method);
                if (uris == null) {
                    uris = new String[]{""};
                }

                if (method.getAnnotation(ResourcesRoleMapping.class) != null
                        || beanClass.getAnnotation(ResourcesRoleMapping.class) != null) {
                    //当前都是有映射,先吃method的,没有再吃bean的
                    ResourcesRoleMapping resourcesRoleMapping = null;

                    if (method.getAnnotation(ResourcesRoleMapping.class) != null) {
                        //吃method
                        resourcesRoleMapping = method.getAnnotation(ResourcesRoleMapping.class);
                    } else {
                        //吃bean
                        resourcesRoleMapping = beanClass.getAnnotation(ResourcesRoleMapping.class);
                    }

                    String[] excludeRoleNames = resourcesRoleMapping.excludeRoleNames();
                    String[] includeRoleNames = resourcesRoleMapping.includeRoleNames();
                    //上面两者不可同时存在
                    if (excludeRoleNames.length != 0 && includeRoleNames.length != 0) {
                        throw new AutoConfigException("excludeRoleNames和includeRoleNames不可同时配置");
                    }
                    if (excludeRoleNames.length == 0 && includeRoleNames.length == 0) {
                        throw new AutoConfigException("excludeRoleNames和includeRoleNames不可以都为空");
                    }
                    String resourceName = resourcesRoleMapping.resourceName();

                    for (String secondaryUri : secondaryUris) {
                        for (String uri : uris) {

                            if (uri.contains("{")) {
                                uri = uri.substring(0, uri.indexOf('{')) + "*";
                            }

                            //资源表已经ok 批量插入后,然后对象是否就有了id 对的
                            AdminResources adminResources = new AdminResources(resourceName, baseUri + secondaryUri + uri);
                            adminResourcesList.add(adminResources);

                            Set<Integer> set = new HashSet<>();
                            if (includeRoleNames.length != 0) {
                                for (String includeRoleName : includeRoleNames) {
                                    if (roleIdMap.keySet().contains(includeRoleName)) {
                                        //包含,则加入
                                        Integer roleId = roleIdMap.get(includeRoleName);
                                        if (roleId == null) {
                                            log.error("方法:" + method.getName() + ",指定的includeRoleName不存在");
                                        } else {
                                            set.add(roleId);
                                        }
                                    }
                                }
                            } else {
                                //排除当前角色
                                for (String excludeRoleName : excludeRoleNames) {
                                    if (!roleIdMap.keySet().contains(excludeRoleName)) {
                                        //不包含,则加入
                                        Integer roleId = roleIdMap.get(excludeRoleName);
                                        if (roleId == null) {
                                            log.error("方法:" + method.getName() + ",指定的excludeRoleName不存在");
                                        } else {
                                            set.add(roleId);
                                        }
                                    }
                                }
                            }
                            adminResourcesSetMap.put(adminResources, set);

                        }
                    }

                }

            }

        }

        //是否有id? 有的
        this.adminResourcesService.saveBatch(adminResourcesList);

        List<AdminRoleResources> adminRoleResourcesList = new ArrayList<>();

        for (int i = 0; i < adminResourcesList.size(); i++) {
            AdminResources adminResources = adminResourcesList.get(i);
            Set<Integer> set = adminResourcesSetMap.get(adminResources);
            for (Integer roleId : set) {
                AdminRoleResources adminRoleResources = new AdminRoleResources(roleId, adminResources.getId());
                adminRoleResourcesList.add(adminRoleResources);
            }
        }

        //是否成功? 成功
        this.adminRoleResourcesService.saveBatch(adminRoleResourcesList);

        return this;
    }

    @SuppressWarnings("all")
    public static String[] getRequestMappingValuesWithMethod(Method method) {
        // get post delete put
        GetMapping getAnnotation = method.getAnnotation(GetMapping.class);
        if (getAnnotation != null) {
            return getAnnotation.path().length == 0 ? getAnnotation.value() : getAnnotation.path();
        }
        PostMapping postAnnotation = method.getAnnotation(PostMapping.class);
        if (postAnnotation != null) {
            return postAnnotation.path().length == 0 ? postAnnotation.value() : postAnnotation.path();
        }
        DeleteMapping deleteAnnotation = method.getAnnotation(DeleteMapping.class);
        if (deleteAnnotation != null) {
            return deleteAnnotation.path().length == 0 ? deleteAnnotation.value() : deleteAnnotation.path();
        }
        PutMapping putAnnotation = method.getAnnotation(PutMapping.class);
        if (putAnnotation != null) {
            return putAnnotation.path().length == 0 ? putAnnotation.value() : putAnnotation.path();
        }
        return new String[]{};
    }

    public static String[] getRequestMappingValuesWithClass(Class clazz) {
        // get post delete put
        RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
        if (requestMapping != null && requestMapping.value().length != 0) {
            return requestMapping.value();
        }
        if (requestMapping != null && requestMapping.path().length != 0) {
            return requestMapping.path();
        }
        return new String[]{""};
    }

}
